package com.bestcem.xm.survey.job;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.bestcem.xm.componet.redis.service.RedisService;
import com.bestcem.xm.componet.xxljob.annotation.XxlRegister;
import com.bestcem.xm.survey.convert.grpc.SurveyGrpcConvertMapper;
import com.bestcem.xm.survey.entity.SurveyRespondent;
import com.bestcem.xm.survey.entity.SurveyRespondentCount;
import com.bestcem.xm.survey.entity.SurveyRespondentData;
import com.bestcem.xm.survey.entity.SurveyRespondentWeixin;
import com.bestcem.xm.survey.grpc.client.QdesGrpcClient;
import com.bestcem.xm.survey.grpc.client.UserGrpcClient;
import com.bestcem.xm.survey.grpc.client.dto.GroupDto;
import com.bestcem.xm.survey.grpc.client.dto.ProjectDto;
import com.bestcem.xm.survey.mapper.SurveyRespondentCountMapper;
import com.bestcem.xm.survey.mapper.SurveyRespondentDataMapper;
import com.bestcem.xm.survey.mapper.SurveyRespondentMapper;
import com.bestcem.xm.survey.mapper.SurveyRespondentWeixinMapper;
import com.bestcem.xm.survey.mq.send.biz.SurveyMessageSendService;
import com.bestcem.xm.survey.service.SurveyCommonService;
import com.bestcem.xm.survey.util.Ip2RegionUtil;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

import static com.bestcem.xm.survey.enums.RespondentClientTypeEnum.RESPONDENT_CLIENT_TYPE_UNKNOWN;
import static com.bestcem.xm.survey.enums.RespondentDataStatusEnum.RESPONDENT_DATA_STATUS_FORMAL;
import static com.bestcem.xm.survey.enums.RespondentDataStatusEnum.RESPONDENT_DATA_STATUS_TEST;
import static com.bestcem.xm.survey.enums.RespondentSourceEnum.RESPONDENT_SOURCE_PREVIEW;
import static com.bestcem.xm.survey.enums.RespondentStatusEnum.RESPONDENT_STATUS_BEGIN;
import static com.bestcem.xm.survey.enums.RespondentStatusEnum.RESPONDENT_STATUS_OPEN;
import static com.bestcem.xm.survey.grpc.v1.services.RespondentWeiXinWxSex.RESPONDENTWEIXIN_WX_SEX_UNKNOWN_VALUE;
import static com.bestcem.xm.survey.util.SurveyConstant.RESP_FINISH_COUNT_FORMAT_KEY;


@Component
@RequiredArgsConstructor
@Slf4j
public class SurveyJobService {

    private @NonNull QdesGrpcClient qdesGrpcClient;

    private @NonNull SurveyRespondentMapper surveyRespondentMapper;

    private @NonNull SurveyCommonService surveyCommonService;

    private @NonNull SurveyGrpcConvertMapper surveyGrpcConvertMapper;

    private @NonNull RedisService redisService;

    private @NonNull SurveyRespondentCountMapper surveyRespondentCountMapper;

    private @NonNull UserGrpcClient userGrpcClient;

    private @NonNull SurveyRespondentWeixinMapper surveyRespondentWeixinMapper;

    private @NonNull SurveyRespondentDataMapper surveyRespondentDataMapper;

    private @NonNull SurveyMessageSendService surveyMessageSendService;

    @XxlJob(value = "survey-check-limit-time-job")
    @XxlRegister(cron = "${app.survey.task.check-limit-time-cron:0 0/10 * * * ?}", triggerStatus = 1, jobDesc = "验证问卷是否超过答题时限")
    public void checkLimitTime() {
        log.info("[survey] checkLimitTime开始");
        JSONObject customAttr = new JSONObject();
        customAttr.put("switch_limit", true);
        List<String> projectIdListList = qdesGrpcClient.qdesDistinctProjectIds(customAttr.toJSONString());
        for (String project_id : projectIdListList) {
            ProjectDto projectDto = surveyGrpcConvertMapper.toProjectDto(qdesGrpcClient.getProject(project_id));
            LambdaQueryWrapper<SurveyRespondent> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(SurveyRespondent::getProjectId, project_id);
            queryWrapper.eq(SurveyRespondent::getStatus, RESPONDENT_STATUS_BEGIN.getValue());
            queryWrapper.eq(SurveyRespondent::getDataStatus, RESPONDENT_DATA_STATUS_FORMAL.getValue());
            List<SurveyRespondent> respondentList = surveyRespondentMapper.selectList(queryWrapper);
            for (SurveyRespondent data : respondentList) {
                try {
                    surveyCommonService.queryTimeV2(projectDto, data, data.getSeq().intValue());
                } catch (Exception e) {
                    log.error("celery query_time error, seq={}", data.getSeq());
                }
            }
        }
        log.info("[survey] checkLimitTime结束");
    }

    @XxlJob(value = "survey-flush_resp_count_redis_to_db-job")
    @XxlRegister(cron = "${app.survey.task.flush-resp-count-redis-to-db-cron:0 0 3 * * ?}", triggerStatus = 1, jobDesc = "更新答卷完成统计")
    public void flushRespCountRedisToDb() {
        log.info("[survey] 更新答卷完成统计开始");
        List<String> project_ids = new ArrayList<>();
        redisService.scanMatch("resp_finish_count:*", 200).forEach(key -> project_ids.add(key.split(":")[1]));

        for (String project_id : project_ids) {
            String p_key = String.format(RESP_FINISH_COUNT_FORMAT_KEY, project_id);
            int count_finish = 0;
            Object result = redisService.get(p_key);
            redisService.del(p_key);
            if (Objects.nonNull(result)) {
                count_finish = Integer.parseInt(result.toString());
            }
            LambdaQueryWrapper<SurveyRespondentCount> queryWrapper = new LambdaQueryWrapper<>();
            //project_id是唯一索引
            queryWrapper.eq(SurveyRespondentCount::getProjectId, project_id);
            SurveyRespondentCount surveyRespondentCount = surveyRespondentCountMapper.selectOne(queryWrapper);
            SurveyRespondentCount respondentCount = new SurveyRespondentCount();
            if (Objects.isNull(surveyRespondentCount)) {
                respondentCount.setProjectId(project_id);
                respondentCount.setCountFinish(count_finish);
                respondentCount.setCountDoing(0);
                respondentCount.setCountBackup(0);
                respondentCount.setCountScreenOut(0);
                respondentCount.setCountQuotaFull(0);
                respondentCount.setCountUnfinish(0);
                surveyRespondentCountMapper.insert(respondentCount);
            } else {
                respondentCount.setCountFinish(count_finish + surveyRespondentCount.getCountFinish());
                respondentCount.setId(surveyRespondentCount.getId());
                surveyRespondentCountMapper.updateById(respondentCount);
            }
        }
        log.info("[survey] 更新答卷完成统计结束");
    }

    @XxlJob(value = "survey-save-unsubmited-answer-job")
    @XxlRegister(cron = "${app.survey.task.save-unsubmited-answer-cron:0 0/10 * * * ?}", triggerStatus = 1, jobDesc = "同步未提交答卷信息")
    public void saveUnsubmitedAnswer() {
        log.info("[survey] 同步未提交答卷信息开始");
        redisService.sunionstore("answered_pid2", "answered_pid", "answered_pid2");
        redisService.sdiffstore("answered_pid", "answered_pid", "answered_pid2");
        while (true) {
            Object pid = redisService.setPop("answered_pid2");
            if (pid == null) {
                break;
            }
            try {
                saveProjectAnswers(pid.toString());
            } catch (Exception e) {
                log.error("save_project_answers failed! pid={}", pid);
                redisService.setAdd("answered_pid", pid);
            }
        }
        log.info("[survey] 同步未提交答卷信息结束");
    }

    private void saveProjectAnswers(String pid) {
        String key = "seq_" + pid;
        String key2 = String.format("seq_%s_2", pid);
        ProjectDto projectDto = surveyGrpcConvertMapper.toProjectDto(qdesGrpcClient.getProject(pid));
        //key2 = key + key2
        redisService.sunionstore(key2, key, key2);
        //key = key - key2
        redisService.sdiffstore(key, key, key2);
        while (true) {
            Object seqobj = redisService.setPop(key2);
            if (Objects.isNull(seqobj)) {
                break;
            }
            Integer seq = (Integer) seqobj;
            int version;
            int source;
            JSONObject answers = surveyCommonService.getAnsweredData(seq);
            if (!answers.isEmpty()) {
                try {
                    version = Integer.parseInt(answers.remove("version").toString());
                    source = Integer.parseInt(answers.remove("source").toString());
                } catch (Exception e) {
                    //version和source一定会保存，如果为空则跳过
                    continue;
                }
                String group_code = (String) Optional.ofNullable(answers.remove("group_code")).orElse("");
                answers.remove("pid");
                String ip = (String) answers.remove("ip");
                String user_agent = (String) answers.remove("user_agent");
                String deliver_id = (String) answers.remove("deliver_id");
                String ded_code = (String) answers.remove("ded_code");
                String dlvr_rid = (String) answers.remove("dlvr_rid");
                JSONObject wx_info = new JSONObject();
                String deliver_group_id = null, member_id = null;
                //判断是否需要记录用户微信信息
                if (Objects.equals(true, JSONObject.parseObject(projectDto.getCustomAttr()).getBooleanValue("wxInfo_record"))) {
                    String wx_user_info = (String) answers.remove("wx_user_info");
                    if (StrUtil.isNotEmpty(wx_user_info) && CollUtil.isNotEmpty(JSONObject.parseObject(wx_user_info))) {
                        wx_info = JSONObject.parseObject(wx_user_info);
                    }
                }
                if (StrUtil.isNotEmpty(deliver_id)) {
                    deliver_group_id = (String) Optional.ofNullable(answers.remove("deliver_g_id")).orElse("");
                    member_id = (String) Optional.ofNullable(answers.remove("member_id")).orElse("");
                }
                String again_deliver_record_id = (String) answers.remove("again_deliver_record_id");
                String person_id = (String) answers.remove("person_id");
                JSONArray question_list = JSONArray.parseArray((String) Optional.ofNullable(answers.remove("question_list")).orElse("[]"));
                int status = (Integer) Optional.ofNullable(answers.remove("status")).orElse(RESPONDENT_STATUS_BEGIN.getValue());
                JSONObject rspd_answers = new JSONObject();
                String prefix = "custom_data__";
                JSONObject custom_answers = new JSONObject();
                int prelen = prefix.length();
                answers.forEach((k, v) -> {
                    if (NumberUtil.isNumber(k)) {
                        rspd_answers.put(k, v);
                    }
                    if (StrUtil.startWith(k, prefix)) {
                        custom_answers.put(k.substring(prelen), v);
                    }
                });
                try {
                    SurveyRespondent rspd = saveUnsubmitedRspd(projectDto, seq, status, version, rspd_answers, ip, user_agent, group_code, source, member_id, deliver_id, deliver_group_id, person_id, custom_answers, again_deliver_record_id, wx_info, ded_code, null, question_list, dlvr_rid);
                    JSONObject props = new JSONObject();
                    props.put("version", rspd.getVersion());
                    props.put("group_id", rspd.getGroupId());
                    props.put("source", rspd.getSource());
                    props.put("status", rspd.getStatus());
                    if (StrUtil.isNotBlank(deliver_id)) {
                        props.put("deliver_id", deliver_id);
                    }
                    if (StrUtil.isNotBlank(deliver_group_id)) {
                        props.put("deliver_group_id", deliver_group_id);
                    }
                    if (StrUtil.isNotBlank(member_id)) {
                        props.put("member_id", member_id);
                    }
                    JSONObject open_props = new JSONObject();
                    if (!CollectionUtils.isEmpty(custom_answers)) {
                        custom_answers.forEach((k, v) -> open_props.put("custom_data__" + k, v));
                    }
                    surveyCommonService.saveRspdProp(pid, seq, answers, props, open_props, rspd.getBeginTime(), status);
                    JSONObject mq_data = surveyCommonService.getRespondentMqData(rspd, rspd_answers, custom_answers, null);
                    surveyMessageSendService.surveyRespondentSyncSend(mq_data);
                    surveyCommonService.memberFootPrintMq(rspd, rspd_answers, custom_answers);
                    //如果用户在Respondent数据生成之前停止操作，则无法对该问卷进行答题时限控制，在此调用此方法，可以让celery在执行写入时调用进行验证
                    try {
                        surveyCommonService.queryTimeV2(projectDto, rspd, seq);
                    } catch (Exception e) {
                        log.error("celery query_time error, seq={},", seq, e);
                    }
                } catch (Exception e) {
                    log.error("save_unsubmited_rspd failed! {}, {}, {}, {}, {}, {}", pid, seq, version, answers.toJSONString(), group_code, source, e);
                }
            }
        }
    }

    private SurveyRespondent saveUnsubmitedRspd(ProjectDto projectDto, Integer seq, int status, int version, JSONObject rspd_answers, String ip, String user_agent, String group_code, int source, String member_id, String deliver_id, String deliver_group_id, String person_id, JSONObject custom_answers, String again_deliver_record_id, JSONObject wx_info, String ded_code, String idy_sid, JSONArray question_list, String dlvr_rid) {
        LocalDateTime created = surveyCommonService.getRspdCreated(seq);
        String group_id = "";
        String project_id = projectDto.getId();
        //if (StrUtil.isNotEmpty(group_code)) {
        //    List<Group> groups = userGrpcClient.listGroups(projectDto.getCompanyId(), group_code, Collections.singletonList(GROUP_STATUS_ACTIVATED.getIndex()));
        //    if (groups.size() == 1) {
        //        group_id = groups.get(0).getId();
        //    }
        //}
        if (!ObjectUtils.isEmpty(group_code)) {
            JSONObject params = new JSONObject();
            params.put("orgID", projectDto.getCompanyId());
            params.put("code", group_code);
            GroupDto group = userGrpcClient.getGroup(params);
            if (group != null) {
                group_id = group.getId();
            }
        }

        Long submit_seq = (long) -seq;
        Integer data_status = source == RESPONDENT_SOURCE_PREVIEW.getValue() ? RESPONDENT_DATA_STATUS_TEST.getValue() : RESPONDENT_DATA_STATUS_FORMAL.getValue();
        JSONArray survey_gids = surveyCommonService.getRspdGntList(seq);
        LambdaQueryWrapper<SurveyRespondent> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SurveyRespondent::getProjectId, project_id).eq(SurveyRespondent::getSeq, seq).in(SurveyRespondent::getStatus, RESPONDENT_STATUS_BEGIN.getValue(), RESPONDENT_STATUS_OPEN.getValue());
        SurveyRespondent surveyRespondent = surveyRespondentMapper.selectOne(queryWrapper);
        SurveyRespondent rspd = new SurveyRespondent();
        rspd.setVersion(version);
        rspd.setStatus(status);
        rspd.setDataStatus(data_status);
        rspd.setSubmitSeq(submit_seq);
        rspd.setGroupCode(group_code);
        rspd.setDedCode(ded_code);
        rspd.setIdySid(idy_sid);
        rspd.setGroupId(group_id);
        rspd.setSource(source);
        rspd.setDeliverId(deliver_id);
        rspd.setDeliverGroupId(deliver_group_id);
        rspd.setMemberId(member_id);
        rspd.setAgainDeliverRecordId(again_deliver_record_id);
        rspd.setSurveyGids(ObjectUtils.isEmpty(survey_gids) ? "[]" : survey_gids.toJSONString());
        rspd.setDlvrRid(dlvr_rid);
        rspd.setBeginTime(created);
        rspd.setPersonId(person_id);
        if (StrUtil.isNotEmpty(ip)) {
            rspd.setIp(ip);
            try {
                //中国|江苏省|苏州市|电信
                String cityInfo = Ip2RegionUtil.getCityInfo(ip);
                String[] addr = cityInfo.split("\\|");
                rspd.setIpCountry(addr[0]);
                rspd.setIpProvince(addr[1]);
                rspd.setIpCity(addr[2]);
            } catch (Exception e) {
                log.error("ip归属地查询错误");
                rspd.setIpCountry("局域网");
                rspd.setIpProvince("局域网");
                rspd.setIpCity("局域网");
            }
        }
        if (StrUtil.isNotEmpty(user_agent)) {
            rspd.setUserAgent(user_agent);
            rspd.setClientType(surveyCommonService.getClientType(user_agent));
        }
        if (Objects.isNull(surveyRespondent)) {
            rspd.setProjectId(project_id);
            rspd.setSeq(Long.valueOf(seq));
            if (Objects.isNull(rspd.getClientType())) {
                rspd.setClientType(RESPONDENT_CLIENT_TYPE_UNKNOWN.getValue());
            }
            if (Objects.isNull(rspd.getBeginTime())) {
                rspd.setBeginTime(LocalDateTime.now());
            }
            if (StrUtil.isEmpty(rspd.getSurveyGids())) {
                rspd.setSurveyGids("[]");
            }
            rspd.setIsCopyDemo(false);
            surveyRespondentMapper.insert(rspd);
        } else {
            rspd.setId(surveyRespondent.getId());
            surveyRespondentMapper.updateById(rspd);
        }
        //weixin_info 保存
        if (CollUtil.isNotEmpty(wx_info)) {
            LambdaQueryWrapper<SurveyRespondentWeixin> wrapper = new LambdaQueryWrapper<>();
            //rid是唯一索引
            wrapper.eq(SurveyRespondentWeixin::getRid, surveyRespondent.getId());
            wx_info.put("project_id", project_id);
            wx_info.put("rid", surveyRespondent.getId());
            SurveyRespondentWeixin weixin = wx_info.toJavaObject(SurveyRespondentWeixin.class);
            SurveyRespondentWeixin surveyRespondentWeixin = surveyRespondentWeixinMapper.selectOne(wrapper);
            if (Objects.isNull(surveyRespondentWeixin)) {
                if (Objects.isNull(weixin.getWxSex())) {
                    weixin.setWxSex(RESPONDENTWEIXIN_WX_SEX_UNKNOWN_VALUE);
                }
                surveyRespondentWeixinMapper.insert(weixin);
            } else {
                weixin.setId(surveyRespondentWeixin.getId());
                surveyRespondentWeixinMapper.updateById(weixin);
            }
            rspd.setWxInfo(weixin);
        } else {
            rspd.setWxInfo(new SurveyRespondentWeixin());
        }
        String rspd_id = rspd.getId();
        //如果和提交保存接口冲突可能会覆盖答案
        LambdaQueryWrapper<SurveyRespondentData> wrapper = new LambdaQueryWrapper<>();
        //rid是唯一索引
        wrapper.eq(SurveyRespondentData::getRid, rspd_id);
        SurveyRespondentData surveyRespondentData = surveyRespondentDataMapper.selectOne(wrapper);
        SurveyRespondentData respondentData = new SurveyRespondentData();
        respondentData.setRid(rspd_id);
        respondentData.setSeq(Long.valueOf(seq));
        respondentData.setAnswers(rspd_answers.toJSONString());
        respondentData.setCustomAnswers(custom_answers.toJSONString());
        respondentData.setProjectId(project_id);
        respondentData.setQuestionList(question_list.toJSONString());
        respondentData.setReportUrl("{}");
        respondentData.setHashQListKey(DigestUtil.md5Hex("[]"));

        if (Objects.isNull(surveyRespondentData)) {
            surveyRespondentDataMapper.insert(respondentData);
        } else {
            respondentData.setId(surveyRespondentData.getId());
            surveyRespondentDataMapper.updateById(respondentData);
        }
        if (StrUtil.equals(rspd.getRspdDataId(), respondentData.getId())) {
            SurveyRespondent respondent = new SurveyRespondent();
            respondent.setRspdDataId(respondentData.getId());
            respondent.setId(rspd.getId());
            surveyRespondentMapper.updateById(respondent);
        }
        //订制投放外部参数数据记录到 DeliverUniqueAnswer
        if (status != RESPONDENT_STATUS_BEGIN.getValue()) {
            surveyCommonService.saveDeliverUniqueAnswer(deliver_id, seq, custom_answers);
        }
        return rspd;
    }
}
